#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>        /* For mode constants */
#include <fcntl.h>           /* For O_* constants */
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <assert.h>
#include <string.h>
#include <time.h>

#include "SharedMemory.h"

struct SM_t
{
    void*   m_shmem ;       /*  Pointer for the shared memory       */
    char*   m_path  ;       /*  The path name of shared memory fd   */
    size_t  m_size  ;       /*  The size of the sharem memory       */
};

/*******************************************************************************************************************************************/
/****************************************************    Internal Functions     ************************************************************/
/*******************************************************************************************************************************************/

/********************** Forward Declerations ************************************************/
/*  Function to fix the named given by user to path appropriate to system call of shm_open  */
static char* AllocateAndFixMemName(const char* _memName);

/*  Function for allocating the SM struct and mapping the memory                            */
static SM* AllocateAndInitSMStruct(char* _fixedName, int _fd, size_t _size);
/********************************************************************************************/

static char* AllocateAndFixMemName(const char* _memName)
{
    char* fixName = (char*)malloc(sizeof(char) * (strlen(_memName) + 2) );

    if(!fixName)                                                /*  Check allocation            */
    {
        fprintf(stderr, "error in shMem_name allocation\n");
        return 0;
    }

    *fixName = '/';                                             /*  Put slash in the beginning  */
    strcpy(fixName + 1, _memName);

    return fixName;
}

static SM* AllocateAndInitSMStruct(char* _fixedName, int _fd, size_t _size)
{
    SM* sm = (SM*)malloc(sizeof(SM));
    if(!sm)                                                             /*  Error in allocation                         */
    {
        fprintf(stderr, "error in allocate shared memory struct\n");
        return 0;
    }

    sm->m_shmem = mmap(0     ,                  /*  where to place the mapping, if NULL, then the kernel chooses the address    */
                       _size ,                  /*  length of the mapping                                                       */
                       PROT_READ | PROT_WRITE,  /*  describes the desired memory protection of the mapping                      */
                       MAP_SHARED,              /*  determine if updates to mapping visible to processes mapping same region    */
                       _fd,                     /*  file descriptor                                                             */
                        0);                     /*   offset  in  the  file                                                      */

    if(sm->m_shmem == (void*)-1)                /*  Check error of mmap. If failed return 0                                     */
    {
        perror(0);
        fprintf(stderr, "error in mapping_memory\n");
        free(sm);
        return 0;
    }

    sm->m_path = _fixedName;
    sm->m_size = _size     ;

    return sm;
}

/*******************************************************************************************************************************************/
SM* SMCreate(size_t _size, const char* _memName)
{
    int                 fd          ;
    char*               fixName = 0 ;
    SM*                 sm      = 0 ;
    pthread_mutexattr_t mutAtr      ;

    assert(SM_OVERHEAD == sizeof(pthread_mutex_t) + sizeof(unsigned short));
    assert(_memName);
    assert(!strchr(_memName,'/'));

    fixName = AllocateAndFixMemName(_memName);
    if(!fixName)
    {
        return 0;
    }
    /*  Create shared memory. Gets back fd      */
    fd = shm_open(fixName,                                          /*  path containing open slash                              */
                  O_RDWR | O_CREAT | O_EXCL ,                       /*  oflag is a bit mask created by ORing flags              *
                                                                     *  O_RDWR   Open the object for read-write access.         *
                                                                     *  O_CREAT  Create the shared memory object if not exist.  *
                                                                     *  O_EXCL   If O_CREAT also specified, and a shared memory *
                                                                     *  with the given name already exists, return an error.    */
                  0644      );                                      /*  mode                                                    */

    if (fd < 0)                                                     /*  Fail in open. Can be also if it already created         */
    {
        perror(0);
        fprintf(stderr, "error in SMCreate_open\n");
        goto cleanShm_open;
    }

    if(ftruncate(fd, _size) < 0)                                    /*  Define the size in memory                       */
    {
        perror(0);
        fprintf(stderr, "error in SMCreate_size\n");
        goto cleanFtruncate;
    }

    sm = AllocateAndInitSMStruct(fixName, fd , _size);
    if(!sm)
    {
        goto cleanFtruncate;
    }

    pthread_mutexattr_init(&mutAtr);                                  /*  init attributes of mutex                                */
    pthread_mutexattr_setpshared(&mutAtr, PTHREAD_PROCESS_SHARED);    /*  Set to process shared                                   */
    pthread_mutexattr_settype(&mutAtr, PTHREAD_MUTEX_RECURSIVE);      /*  Only process that locked can unlock. Mutex Re-entrant   */
    if(pthread_mutex_init( (pthread_mutex_t*)sm->m_shmem, &mutAtr))   /*  Init the mutex with the above attributes                */
    {
        fprintf(stderr, "error in initializing mutex\n");
        goto cleanFtruncate;
    }

    /*  Place counter of 2 bytes (unsigned short) to count the attached processes. */
    *( (unsigned short*)( (char*)sm->m_shmem + sizeof(pthread_mutex_t) ) ) = 1;

    close(fd);

    return sm;

    /*  Labels for goto to clean if procedure failed    */
    cleanFtruncate:
        close(fd);
        shm_unlink(fixName);
    cleanShm_open:
        free(fixName);
        return 0;
}


SM* SMAttach(const char* _memName)
{
    int             fd          ;
    char*           fixName = 0 ;
    SM*             sm      = 0 ;
    struct stat     buf         ;

    assert(_memName);
    assert(!strchr(_memName,'/'));

    fixName = AllocateAndFixMemName(_memName);
    if(!fixName)
    {
        return 0;
    }

    fd = shm_open(fixName,                                          /*  path containing open slash                              */
                  O_RDWR | O_EXCL ,                                 /*  oflag is a bit mask created by ORing flags              *
                                                                     *  O_RDWR   Open the object for read-write access.         *
                                                                     *  O_EXCL   If O_CREAT also specified, and a shared memory *
                                                                     *  with the given name already exists, return an error.    */
                  0644      );                                      /*  mode                                                    */

    if(fd < 0)                                                      /*  Error in shm_open                                       */
    {
        perror(0);
        fprintf(stderr, "error in SMAttach_open\n");
        goto cleanShm_open;
    }

    if(fstat(fd, &buf) < 0)                             /*  Error in fstat                              */
    {
        perror(0);
        fprintf(stderr, "error in getting status\n");
        goto cleanFstat;
    }

    sm = AllocateAndInitSMStruct(fixName, fd , buf.st_size);
    if(!sm)
    {
        goto cleanFstat;
    }

    *( (unsigned short*)((char*)sm->m_shmem + sizeof(pthread_mutex_t) ) ) += 1; /*  increment counter by 1      */

    close(fd);

    return sm;

    /*  Labels for goto to clean if procedure failed    */
    cleanFstat:
        close(fd);
    cleanShm_open:
        free(fixName);
        return 0;
}


int SMLock(SM* _sm)
{
    assert(_sm);
    assert(_sm->m_shmem);

    return pthread_mutex_lock((pthread_mutex_t*)_sm->m_shmem);      /*  Lock the mutex and return the lock Return Value     */
}


int SMLockTimeOut(SM* _sm, long _seconds)
{
    struct timespec absTime;

    assert(_sm);
    assert(_sm->m_shmem);

    if(clock_gettime(CLOCK_REALTIME, &absTime))                     /*  Get the current time to struct absTime              */
    {
        perror(0);
        return -1;
    }

    absTime.tv_sec += _seconds;                                     /*  Add the seconds for waiting to curr time            */

    return pthread_mutex_timedlock((pthread_mutex_t*)_sm->m_shmem, &absTime);      /*  Lock the mutex and return the lock Return Value     */
}


int SMUnlock(SM* _sm)
{
    assert(_sm);
    assert(_sm->m_shmem);

    return pthread_mutex_unlock((pthread_mutex_t*)_sm->m_shmem);                   /*  Unlock the mutex and return the lock Return Value   */
}


void* SMGet(SM* _sm)
{
    assert(_sm);
    assert(_sm->m_shmem);

    return (void*)((char*)_sm->m_shmem + sizeof(unsigned short) +sizeof(pthread_mutex_t));
}


void SMDetach(SM* _sm)
{
    unsigned short* countPtr = 0;

    assert(_sm);
    assert(_sm->m_shmem);
    assert(_sm->m_path);

    countPtr = ( (unsigned short*)((char*)_sm->m_shmem + sizeof(pthread_mutex_t) ) );

    assert(*countPtr);      /*  assert decrement counter alreay 0       */
    --(*countPtr);          /*  Decrement counter when process detach   */

    /*  If the counter down to zero -> unlink the shared memory name    */
    if(!(*countPtr))
    {
        assert(!shm_unlink(_sm->m_path));
    }
    assert(!munmap(_sm->m_shmem, _sm->m_size));           /* unmap the memory in virtual memory  */
    free(_sm->m_path);
    free(_sm);
}
/***************************************************************************************************************/
/*********************************************    SM_UTEST    **************************************************/
/***************************************************************************************************************/
#ifdef SM_UTEST

#define IS_SUCCESS(X) !X?"SUCCESS":"FAILURE"   /*  Macro to print on regression tests       */
#define IF_FAIL(X) if(X){printf("FAIL IN %s TEST, IN LINE %d\n",#X,__LINE__); return 1;}
/*  Internal basic check of the parameters      */
/*  Wide test is done outsize with 2 procceses  */
int main()
{
    SM*     sm      ;
    SM*     sm1     ;
    SM*     sm2     ;
    void*   memory  ;
    void*   memory1 ;

    shm_unlink("/smtest");

    sm = SMCreate(50 + SM_OVERHEAD, "smtest");
    memory = SMGet(sm);
    IF_FAIL(SMUnlock(sm) != 1)     /*  unlock without lock before, gets error  */
    IF_FAIL(strcmp(sm->m_path, "/smtest"))
    IF_FAIL((char*)sm->m_shmem != (char*)memory - sizeof(pthread_mutex_t) - sizeof(unsigned short))

    printf("Lock operation: %d\n",SMLock(sm));

    strcpy((char*)memory, "This is a test");
    printf("%s\n", (char*)memory);

    sm1 = SMAttach("smtest");
    memory1 = SMGet(sm1);

    IF_FAIL(strcmp(sm1->m_path, "/smtest"))
    IF_FAIL(strcmp(memory1, "This is a test"))

    sm2 = SMAttach("smtest");

    printf("Unlock operation: %d\n",SMUnlock(sm));

    SMDetach(sm);
    SMDetach(sm1);
    SMDetach(sm2);

    return 0;
}

#endif  /*  SM_UTEST    */
